package com.liuc.task.cluster.demo.task1.factory;

import com.liuc.task.cluster.demo.task1.exception.UnlockFailureException;
import com.liuc.task.cluster.demo.task1.middle.ware.Lock;
import com.liuc.task.cluster.demo.task1.util.RedisTool;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


@Component
public class ProxyFactory {

    @Autowired
    private RedisTool redisTool;

    /**
     * map 中的 Key 为任务名，value 是如果该任务当前的锁是否已经设置了超时时间
     */
    private static Map<String, Boolean> timeOutFlagMap = new HashMap<>();

    private static final Logger logger = LoggerFactory.getLogger(ProxyFactory.class);


    /**
     *
     * @param obj 原对象
     * @return 生成的代理对象
     */
    public Object getProxyObject(Object obj) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        Object result = null;

                        try {
                            result = method.invoke(obj, args);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return result;
                    }
                });
    }


    /**
     * Lock 为稳定的接口，redisLock 可以作为一种具体实现，所以这里命名类 Lock, 变量用 redisLock
     * @param obj
     * @param redisLock
     * @param uuid 用于给每个服务做独立标识
     * @return
     */
    public Object getProxyObject(Object obj, Lock redisLock, String uuid) {
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        String lockName = redisLock.getName();
                        Object result = null;

                        try {
                            if (redisTool.getLock(lockName, redisLock.getValue())) {
                                result = method.invoke(obj, args);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            release(redisLock, uuid);
                        }

                        return result;
                    }

                    private void release(Lock lock, String uuid) throws Exception {
                        // 解锁失败标记
                        Boolean unLockSuccessFlag = true;
                        String lockName = lock.getName();
                        Object lockValue = redisTool.get(lockName);

                        try {
                            if (lockValue != null && StringUtils.isNotBlank(lockValue.toString())) {
                                redisTool.releaseLock(lockName, uuid);

                                // 第二次的时候，如果锁超时存在，需要释放掉
                                if (getLockTimeOutFlag(lockName)) {
                                    timeOutFlagMap.remove(lockName);
                                }
                                logger.info("uuid:" + uuid + " 解锁成功");
                            }

                        } catch (UnlockFailureException e) {
                            unLockSuccessFlag = false;
                            logger.info("uuid:" + uuid + " 非加锁服务，解锁失败");

                        } catch (NullPointerException e) {
                            logger.info("uuid:" + uuid + " 锁已释放");
                        } finally {
                            // 解锁失败，并且再锁超时 map 中不存在，为了防止死锁，将锁超时塞进去，在下次解锁的时候释放
                            if (!unLockSuccessFlag && !getLockTimeOutFlag(lockName)) {
                                timeOutFlagMap.put(lockName, true);
                                redisTool.releaseLockWithTimeout(lockName, lock.getTimeout(), lock.getTimeUnit());
                                logger.info("任务" + lock.getName() + ", uuid：" + redisTool.get(lockName) + "已设置超时时间");
                            }
                        }

                    }

                    private boolean getLockTimeOutFlag(String lockName) {
                        if (timeOutFlagMap.get(lockName) == null) {
                            return false;
                        }
                        return timeOutFlagMap.get(lockName);
                    }
                });
    }
}
